library flutter_rubic;
import 'dart:io';
import 'dart:math' as Math;
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'package:flutter/widgets.dart';
import 'package:meta/meta.dart';
import 'package:triangle/cube.dart';
import 'package:vector_math/vector_math.dart' show Vector3;
import 'package:vector_math/vector_math.dart' as V;
class Cubic3D extends StatefulWidget {
Cubic3D(
{ @required this.size,
@required this.path,
@required this.asset,
this.angleX,
this.angleY,
this.angleZ,
this.zoom = 100.0, this.blocks}) {
if (angleX != null || angleY != null || angleZ != null) {
useInternal = false;
}
specmode = 1;
}
int specmode;
List<Block> blocks;
Size size;
bool asset;
String path;
double zoom;
double angleX;
double angleY;
double angleZ;
bool useInternal = true;
@override
_Cubic3DState createState() => new _Cubic3DState(path, useInternal, asset);
}
//class reflect extends Reflectable {
//
//}
class _Cubic3DState extends State<Cubic3D> {
bool useInternal;
String path;
double angleX = 15.0;
double angleY = 45.0;
double angleZ = 0.0;
double _previousX = 0.0;
double _previousY = 0.0;
double zoom;
String object = "V 1 1 1 1";
Map parsedFile;//= _parseObjString(object);
File file;
_Cubic3DState(this.path, this.useInternal, bool asset) {
if(asset){
rootBundle.loadString(this.path).then((String value) {
setState(() {
object = value;
parsedFile = _parseObjString(object);
});
});
}else{
File file = new File(this.path);
file.readAsString().then((String value) {
setState(() {
object = value;
parsedFile = _parseObjString(object);
});
});
}
}
Map<String, List> _parseObjString(String objString) {
List vertices = <Vector3>[];
List faces = <List<int>>[];
List usemtl = <Color>[];
List<int> face = [];
List<String> lines = objString.split("\n");
var lastUseml;
Vector3 vertex;
var colortbl = {"red":Colors.red, "blue":Colors.blue, "yellow":Colors.yellow,
"green":Colors.green, "white": Colors.white, "brown":Colors.brown, "":Colors.transparent};
lines.forEach((String line) {
line = line.replaceAll(new RegExp(r"\s+$"), "");
List<String> chars = line.split(" ");
// vertex
if (chars[0] == "v") {
vertex = new Vector3(double.parse(chars[1]), double.parse(chars[2]),
double.parse(chars[3]));
vertices.add(vertex);
// face
} else if (chars[0] == "f") {
for (var i = 1; i < chars.length; i++) {
face.add(int.parse(chars[i].split("/")[0]));
}
faces.add(face);
face = [];
usemtl.add(lastUseml);
if(lastUseml!=null) {
lastUseml = null;
}
} else if(chars[0] == "usemtl") {
if(!colortbl.containsKey(chars[1])) {
lastUseml=colortbl[""];
}
else {
lastUseml=colortbl[chars[1]];
}
}
});
return {'vertices': vertices, 'faces': faces, 'usemtl': usemtl};
}
void _updateCube(DragUpdateDetails data) {
if (angleY > 360.0) {
angleY = angleY - 360.0;
}
if (_previousY > data.globalPosition.dx) {
setState(() {
angleY = angleY - 1;
});
}
if (_previousY < data.globalPosition.dx) {
setState(() {
angleY = angleY + 1;
});
}
_previousY = data.globalPosition.dx;
if (angleX > 360.0) {
angleX = angleX - 360.0;
}
if (_previousX > data.globalPosition.dy) {
setState(() {
angleX = angleX - 1;
});
}
if (_previousX < data.globalPosition.dy) {
setState(() {
angleX = angleX + 1;
});
}
_previousX = data.globalPosition.dy;
}
void _updateY(DragUpdateDetails data) {
_updateCube(data);
}
void _updateX(DragUpdateDetails data) {
_updateCube(data);
}
void _touchDown( data) {
setState(() {
widget.specmode = -1;
});
}
void _touchUp(PointerUpEvent data) {
setState(() {
widget.specmode = 1;
});
}
@override
Widget build(BuildContext context) {
return Listener(
child: new GestureDetector(
child: new CustomPaint(
painter: new _ObjectPainter(
widget.size,
parsedFile,
useInternal ? angleX : widget.angleX,
useInternal ? angleY : widget.angleY,
useInternal ? angleZ : widget.angleZ,
widget.zoom, widget.specmode),
size: widget.size,
),
onHorizontalDragUpdate: _updateY,
onVerticalDragUpdate: _updateX,
),
onPointerDown: _touchDown,
onPointerUp: _touchUp,
);
}
}
class _ObjectPainter extends CustomPainter {
double _zoomFactor = 100.0;
final double _rotation = 5.0; // in degrees
double _translation = 0.1 / 100;
final double _scalingFactor = 10.0 / 100.0; // in percent
final double ZERO = 0.0;
double _viewPortX = 0.0, _viewPortY = 0.0;
List<Vector3> vertices;
List<dynamic> faces;
List<dynamic> usemtls;
V.Matrix4 T;
Vector3 camera;
Vector3 light;
double angleX;
double angleY;
double angleZ;
Color color;
Size size;
var parsedFile;
var specmode;
_ObjectPainter(this.size, this.parsedFile, this.angleX, this.angleY, this.angleZ,
this._zoomFactor, this.specmode ) {
_translation *= _zoomFactor;
camera = new Vector3(0.0, 0.0, 200.0);
light = new Vector3(0.0, 0.0, 100.0);
color = new Color.fromARGB(255, 255, 255, 255);
_viewPortX = (size.width / 2).toDouble();
_viewPortY = (size.height / 2).toDouble();
}
bool _shouldDrawFace(List face, Map avg) {
return avg["side"]>0;
}
Vector3 _normalVector3(Vector3 first, Vector3 second, Vector3 third) {
Vector3 secondFirst = new Vector3.copy(second);
secondFirst.sub(first);
Vector3 secondThird = new Vector3.copy(second);
secondThird.sub(third);
return new Vector3(
(secondFirst.y * secondThird.z) - (secondFirst.z * secondThird.y),
(secondFirst.z * secondThird.x) - (secondFirst.x * secondThird.z),
(secondFirst.x * secondThird.y) - (secondFirst.y * secondThird.x));
}
double _scalarMultiplication(Vector3 first, Vector3 second) {
return (first.x * second.x) + (first.y * second.y) + (first.z * second.z);
}
Vector3 _calcDefaultVertex(Vector3 vertex) {
T = new V.Matrix4.translationValues(_viewPortX, _viewPortY, ZERO);
T.scale(_zoomFactor, -_zoomFactor);
T.rotateX(_degreeToRadian(angleX != null ? angleX : 0.0));
T.rotateY(_degreeToRadian(angleY != null ? angleY : 0.0));
T.rotateZ(_degreeToRadian(angleZ != null ? angleZ : 0.0));
return T.transform3(vertex);
}
double _degreeToRadian(double degree) {
return degree * (Math.pi / 180.0);
}
List<dynamic> _drawFace(List<Vector3> verticesToDraw, List<int> face, Color usemtl) {
List<dynamic> list = <dynamic>[];
Paint paint = new Paint();
Vector3 normalizedLight = new Vector3.copy(light).normalized();
var normalVector = _normalVector3(verticesToDraw[face[0] - 1],
verticesToDraw[face[1] - 1], verticesToDraw[face[2] - 1]);
Vector3 jnv = new Vector3.copy(normalVector).normalized();
double koef = _scalarMultiplication(jnv, normalizedLight);
if (koef < 0.0) {
koef = -koef;
}
koef = koef*4/5+0.2;
Color newColor = usemtl; // = new Color.fromARGB(255, 0, 0, 0);
Path path = new Path();
Path bgpath = new Path();
// newColor = newColor.withRed((usemtl.red.toDouble() * koef).round());
// newColor = newColor.withGreen((usemtl.green.toDouble() * koef).round());
// newColor = newColor.withBlue((usemtl.blue.toDouble() * koef).round());
paint.color = newColor;
paint.style = PaintingStyle.fill;
bool lastPoint = false;
double firstVertexX, firstVertexY, secondVertexX, secondVertexY;
double centX=0, centY=0;
if(face.length==4) {
centX = (verticesToDraw[face[0] - 1][0].toDouble()+verticesToDraw[face[2] - 1][0].toDouble())/2;
centY = (verticesToDraw[face[0] - 1][1].toDouble()+verticesToDraw[face[2] - 1][1].toDouble())/2;
}
for (int i = 0; i < face.length; i++) {
if (i + 1 == face.length) {
lastPoint = true;
}
if (lastPoint) {
firstVertexX = verticesToDraw[face[i] - 1][0].toDouble();
firstVertexY = verticesToDraw[face[i] - 1][1].toDouble();
secondVertexX = verticesToDraw[face[0] - 1][0].toDouble();
secondVertexY = verticesToDraw[face[0] - 1][1].toDouble();
} else {
firstVertexX = verticesToDraw[face[i] - 1][0].toDouble();
firstVertexY = verticesToDraw[face[i] - 1][1].toDouble();
secondVertexX = verticesToDraw[face[i + 1] - 1][0].toDouble();
secondVertexY = verticesToDraw[face[i + 1] - 1][1].toDouble();
}
if (i == 0) {
bgpath.moveTo(firstVertexX, firstVertexY);
}
bgpath.lineTo(secondVertexX, secondVertexY);
firstVertexX += (centX-firstVertexX)/10;
firstVertexY += (centY-firstVertexY)/10;
secondVertexX += (centX-secondVertexX)/10;
secondVertexY += (centY-secondVertexY)/10;
if (i == 0) {
path.moveTo(firstVertexX, firstVertexY);
}
path.lineTo(secondVertexX, secondVertexY);
}
var z = 0.0;
face.forEach((int x) {
z += verticesToDraw[x - 1].z;
});
bgpath.close();
path.close();
list.add(path);
list.add(bgpath);
list.add(paint);
return list;
}
@override
void paint(Canvas canvas, Size size) {
if(parsedFile==null) return;
vertices = parsedFile["vertices"];
faces = parsedFile["faces"];
usemtls = parsedFile["usemtl"];
List<Vector3> verticesToDraw = [];
vertices.forEach((vertex) {
verticesToDraw.add(new Vector3.copy(vertex));
});
for (int i = 0; i < verticesToDraw.length; i++) {
verticesToDraw[i] = _calcDefaultVertex(verticesToDraw[i]);
}
final List<Map> avgOfZ = <Map>[];
for (int i = 0; i < faces.length; i++) {
List<int> face = faces[i];
var positiveside =
-(verticesToDraw[face[3] - 1].y-verticesToDraw[face[2] - 1].y)*
(verticesToDraw[face[2] - 1].x-verticesToDraw[face[1] - 1].x);
if(positiveside == 0) {
positiveside = -(verticesToDraw[face[2] - 1].y-verticesToDraw[face[1] - 1].y)*
(verticesToDraw[face[1] - 1].x-verticesToDraw[face[0] - 1].x);
}
// positiveside*=specmode;
double z = 0.0;
face.forEach((int x) {
z += verticesToDraw[x - 1].z;
});
Map data = <String, dynamic>{
"index": i,
"z": z,
"side": positiveside,
};
avgOfZ.add(data);
}
avgOfZ.sort((Map a, Map b) => a['z'].compareTo(b['z']));
Paint bgpaint = new Paint();
bgpaint.color = Colors.black;
bgpaint.style = PaintingStyle.fill;
Paint trpaint = new Paint();
trpaint.color = Color.fromARGB(100,200,200,200);
trpaint.style = PaintingStyle.stroke;
for (int i = 0; i < faces.length; i++) {
var ind = avgOfZ[i]["index"];
List face = faces[ind];
if (_shouldDrawFace(face, avgOfZ[i]) || specmode==-1) {
Color fcolor = usemtls[ind];
if(specmode==-1 && avgOfZ[i]['side']>0) {
fcolor = Color.fromARGB(50,255,255,255);
}
final List<dynamic> faceProp = _drawFace(verticesToDraw, face, fcolor);
if(specmode==-1 && avgOfZ[i]['side']>0) {
canvas.drawPath(faceProp[1], trpaint);
}
else {
canvas.drawPath(faceProp[1], bgpaint);
canvas.drawPath(faceProp[0], faceProp[2]);
}
}
}
}
@override
bool shouldRepaint(_ObjectPainter old) =>
old.parsedFile != this.parsedFile ||
old.angleX != angleX ||
old.angleY != angleY ||
old.angleZ != angleZ ||
old._zoomFactor != _zoomFactor ||
old.specmode != specmode;
}